{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 12: Entrenar una Red Neuronal Encriptada con Datos Encriptados \n",
    "\n",
    "En este cuarderno vamos a usar todas las técnicas que aprendimos hasta ahora para entrenar redes neuronales (y hacer predicciones) cuando ambos el modelo y los datos están encriptados.\n",
    "\n",
    "En particular, presentamos nuestro motor Autograd personalizado, el cual funciona en cálculos encriptados. \n",
    "\n",
    "Autores:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "- Jason Paumier - Github: [@Jasopaum](https://github.com/Jasopaum)\n",
    "- Théo Ryffel - Twitter: [@theoryffel](https://twitter.com/theoryffel)\n",
    "\n",
    "Traducción\n",
    "- Carlos Salgado - Github: [@socd06](https://github.com/socd06)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Paso 1: Crear Trabajadores y Datos de juguete"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "import syft as sy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Preparar todo\n",
    "hook = sy.TorchHook(torch) \n",
    "\n",
    "alice = sy.VirtualWorker(id=\"alice\", hook=hook)\n",
    "bob = sy.VirtualWorker(id=\"bob\", hook=hook)\n",
    "james = sy.VirtualWorker(id=\"james\", hook=hook)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Un conjunto de Datos de juguete\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]])\n",
    "target = torch.tensor([[0],[0],[1],[1.]])\n",
    "\n",
    "# Un Modelo de juguete\n",
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 2)\n",
    "        self.fc2 = nn.Linear(2, 1)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.fc1(x)\n",
    "        x = F.relu(x)\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "model = Net()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Paso 2: Encriptar el Modelo y los Datos\n",
    "\n",
    "La encriptación aquí viene en dos pasos. Dado que la Computación Segura Multiparte sólo funciona en enteros, para poder operar en números con punto decimal (tales como los pesos y activaciones), debemos codificar todos nuestros números usando Precisión Fija, la cual nos dará varios bits de precisión decimal. Hacemos esto llamando a .fix_precision().\n",
    "\n",
    "Entonces podemos llamar a .share() como lo hicimos para otras demostraciones, lo cual encriptará todos los valores compartiéndolos entre Alice y Bob. Nota que también ponemos requires_grad a True lo cual agrega un método autograd especial para datos encriptados. Ciertamente, dado que la Computación Segura Multiparte no funciona en valores flotantes, no podemos usar el usual autograd en PyTorch. Por lo tanto, necesitamos agregar un nodo AutogradTensor especial que calcule el grafo del gradiente para la retropropagación. Puedes imprimir cualquier parte de este elemento para ver que incluye un AutogradTensor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Codificamos todo\n",
    "data = data.fix_precision().share(bob, alice, crypto_provider=james, requires_grad=True)\n",
    "target = target.fix_precision().share(bob, alice, crypto_provider=james, requires_grad=True)\n",
    "model = model.fix_precision().share(bob, alice, crypto_provider=james, requires_grad=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Paso 3: Entrenamiento\n",
    "\n",
    "Y ahora podemos entrenar usando la lógica tensorial simple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "opt = optim.SGD(params=model.parameters(),lr=0.1).fix_precision()\n",
    "\n",
    "for iter in range(20):\n",
    "    # 1) Borrar gradientes previos (si existen) \n",
    "    opt.zero_grad()\n",
    "\n",
    "    # 2) Hacer una predicción\n",
    "    pred = model(data)\n",
    "\n",
    "    # 3) Calcular que tanto fallamos (la pérdida)\n",
    "    loss = ((pred - target)**2).sum()\n",
    "\n",
    "    # 4) Averiguar que pesos nos hicieron fallar\n",
    "    loss.backward()\n",
    "\n",
    "    # 5) Cambiar dichos pesos\n",
    "    opt.step()\n",
    "\n",
    "    # 6) Imprimir nuestro progreso\n",
    "    print(loss.get().float_precision())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La perdida ciertamente disminuyó!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Impacto de la Precisión Fija\n",
    "Tal vez te estés preguntando como encriptar todo impacta la disminución de la pérdida. De hecho, porque el cálculo teórico es el mismo, los números están muy cerca al entrenamiento no-encriptado. Puedes verificar esto corriendo el mismo ejemplo sin la encripción y con una inicialización determinística del modelo como este en el modelo `__init__`:\n",
    "\n",
    "```\n",
    "with torch.no_grad():\n",
    "    self.fc1.weight.set_(torch.tensor([[ 0.0738, -0.2109],[-0.1579,  0.3174]], requires_grad=True))\n",
    "    self.fc1.bias.set_(torch.tensor([0.,0.1], requires_grad=True))\n",
    "    self.fc2.weight.set_(torch.tensor([[-0.5368,  0.7050]], requires_grad=True))\n",
    "    self.fc2.bias.set_(torch.tensor([-0.0343], requires_grad=True))\n",
    "```\n",
    "\n",
    "La ligera diferencia que podrás observar es debido al redondeo de los valores realizado durante la transformación a Precisión Fija. La `precision_fractional` es 3 y si se reduce a 2 la divergencia con entrenamiento de texto claro aumenta, mientras que si elijes `precision_fractional = 4` se reduce. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ¡¡Felicidades!! - Hora de Unirte a la Comunidad\n",
    "\n",
    "¡Felicidades por completar este cuaderno tutorial! Si lo disfrutaste y te gustaría unirte al movimiento hacia la preservación de la privacidad, propiedad decentralizada de Inteligencia Artificial y la Inteligencia Artificial de Cadena de Valores (de Datos), puedes hacerlo de las siguientes maneras:\n",
    "\n",
    "### Dale una Estrella a PySyft en Github\n",
    "\n",
    "¡La forma más fácil de ayudar a nuestra comunidad es guardando con una estrella los Repos! Esto ayuda a crear consciencia de las geniales herramientas que estamos construyendo.\n",
    "\n",
    "- [Guardar con Estrella a PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### ¡Únete a nuestro Slack!\n",
    "\n",
    "¡La mejor manera de estar al día con los últimos avances es unirte a nuestra comunidad! Puedes hacerlo llenando la forma en [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### ¡Únete a un Proyecto de Programación!\n",
    "\n",
    "¡La mejor manera de contribuir a nuestra comunidad es haciéndote un contribuidor de código! Puedes ir a PySyft Github Issues en cualquier momento y filtrar por \"Projects\". Esto te mostrará todos los Tickets de alto nivel, dando un resumen de los proyectos a los que puedes unirte. Si no quieres unirte a un proyecto, pero te gustaría programar un poco, puedes buscar mini-proyectos únicos buscando en Github Issues con \"good first issue\".\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Donaciones\n",
    "\n",
    "Si no tienes tiempo para contribuir a nuestra base de código, pero quieres brindarnos tu apoyo, puedes respaldarnos en nuestro Open Collective. Todas las donaciones van hacia nuestro alojamiento web y otros gastos de la comunidad como hackatones y reuniones. \n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
